package com.nageoffer.jobtrain.judge.service.impl;

import cn.hutool.core.date.StopWatch;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.ExecCreateCmdResponse;
import com.github.dockerjava.api.command.StatsCmd;
import com.nageoffer.jobtrain.common.web.exception.ClientException;
import com.nageoffer.jobtrain.judge.common.callback.DockerStartResultCallback;
import com.nageoffer.jobtrain.judge.common.callback.StatisticsCallback;
import com.nageoffer.jobtrain.judge.common.config.DockerSandBoxPool;
import com.nageoffer.jobtrain.judge.common.constant.SystemConstant;
import com.nageoffer.jobtrain.judge.common.constant.UserJudgeConstant;
import com.nageoffer.jobtrain.judge.common.enums.ExecuteJudeCodeEnums;
import com.nageoffer.jobtrain.judge.common.enums.UserPassOjQuestionEnums;
import com.nageoffer.jobtrain.judge.common.result.CompileResult;
import com.nageoffer.jobtrain.judge.common.result.SandBoxExecuteResult;
import com.nageoffer.jobtrain.judge.service.SandBoxPoolService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 容器池实现代码编译、运行、匹配测试用例逻辑
 * author：唐晨
 */
@Service
@RequiredArgsConstructor
public class SandBoxPoolServiceImpl implements SandBoxPoolService {
    private final DockerClient dockerClient;
    private final DockerSandBoxPool dockerSandBoxPool;

    /**
     * 容器ID
     */
    private String containerId;

    /**
     * 用户容器挂载卷路径
     */
    private String userCodeFileName;

    @Value("${sand-box.limit.time}")
    private Long timeLimit;

    @Override
    public SandBoxExecuteResult executeJudeCode(String phoneNumber, String totalCode, List<String> inputList) {
        // 1、先从容器池中获取一个容器，没有多余的容器就阻塞等待
        containerId = dockerSandBoxPool.getContainer();
        // 2、创建用户代码文件
        createUserCodeFile(totalCode);
        // 3、进行用户提交代码的编译
        CompileResult compileResult = compileCodeByDocker();
        if (!compileResult.isCompiled()) {
            // 如果编译失败，不直接删除容器，直接讲容器归还给容器池以及代码文件信息
            dockerSandBoxPool.returnContainer(containerId);
            deleteUserCodeFile();
            return SandBoxExecuteResult.fail(ExecuteJudeCodeEnums.COMPILE_FAILED, compileResult.getErrorMessage());
        }
        // 4、此时编译逻辑正确，执行代码运行逻辑 -将数据库中预期输入测试用例，获取输出结果
        return executeJavaCodeByDocker(inputList);
    }

    private SandBoxExecuteResult executeJavaCodeByDocker(List<String> inputList) {
        List<String> outList = new ArrayList<>(); //记录输出结果
        long maxMemory = 0L;  //最大占用内存
        long maxUseTime = 0L; //最大运行时间
        //执行用户代码
        for (String inputArgs : inputList) {
            String cmdId = createExecCmd(UserJudgeConstant.DOCKER_JAVA_EXEC_CMD, inputArgs, containerId);
            StopWatch stopWatch = new StopWatch();
            StatsCmd statsCmd = dockerClient.statsCmd(containerId); //启动监控
            StatisticsCallback statisticsCallback = statsCmd.exec(new StatisticsCallback());
            stopWatch.start();
            DockerStartResultCallback resultCallback = new DockerStartResultCallback();
            try {
                dockerClient.execStartCmd(cmdId)
                        .exec(resultCallback)
                        .awaitCompletion(timeLimit, TimeUnit.SECONDS);
                if (UserPassOjQuestionEnums.FAILED.equals(resultCallback.getUserPassOjQuestionEnums())) {
                    return SandBoxExecuteResult.fail(ExecuteJudeCodeEnums.NOT_ALL_PASSED, resultCallback.getErrorMessage());
                }
            } catch (Throwable ex) {
                throw new ClientException("代码运行时异常：" + ex);
            } finally {
                statsCmd.close();  // 关闭监控
                stopWatch.stop();
            }
            long userTime = stopWatch.getLastTaskTimeMillis(); //记录耗时
            maxUseTime = Math.max(userTime, maxUseTime);
            Long memory = statisticsCallback.getMaxMemory();
            if (memory != null) {
                maxMemory = Math.max(maxMemory, memory);
            }
            String resultMessage = resultCallback.getMessage();
            if (resultMessage == null) {
                resultMessage = "执行超时或未生成输出。";
            }
            outList.add(resultMessage.trim());
        }
        dockerSandBoxPool.returnContainer(containerId); // 归还容器
        deleteUserCodeFile(); //清理文件
        return getSanBoxResult(inputList, outList, maxMemory, maxUseTime);
    }

    /**
     * 填充执行代码逻辑返回结果
     */
    private SandBoxExecuteResult getSanBoxResult(List<String> inputList, List<String> outList,
                                                 long maxMemory, long maxUseTime) {
        if (inputList.size() != outList.size()) {
            //输入用例数量 不等于 输出用例数量  属于执行异常
            return SandBoxExecuteResult.fail(ExecuteJudeCodeEnums.NOT_ALL_PASSED, outList, maxMemory, maxUseTime);
        }
        return SandBoxExecuteResult.success(ExecuteJudeCodeEnums.SUCCEED, outList, maxMemory, maxUseTime);
    }

    // 使用 docket中的 jdk进行对代码编译逻辑
    private CompileResult compileCodeByDocker() {
        String cmdId = createExecCmd(UserJudgeConstant.DOCKER_JAVAC_CMD, null, containerId);
        DockerStartResultCallback resultCallback = new DockerStartResultCallback();
        // 编译结果返回上层逻辑实体
        CompileResult compileResult = new CompileResult();
        try {
            dockerClient.execStartCmd(cmdId) // 执行编译命令
                    .exec(resultCallback) // 编译结果回调
                    .awaitCompletion();  // 阻塞等待编译结束
            if (UserPassOjQuestionEnums.FAILED.equals(resultCallback.getUserPassOjQuestionEnums())) {
                compileResult.setCompiled(false);
                compileResult.setErrorMessage(resultCallback.getErrorMessage());
            } else {
                compileResult.setCompiled(true);
            }
            return compileResult;
        } catch (Throwable ex) {
            //此处可以直接抛出 已做统一异常处理  也可再做定制化处理
            throw new ClientException("编译异常：" + ex);
        }
    }

    // 创建使用 docker 编译命令
    private String createExecCmd(String[] javaCmdArr, String inputArgs, String containerId) {
        if (!StrUtil.isEmpty(inputArgs)) {
            //当入参不为空时拼接入参 - 这里将输入参数进行分割，可参考牛客、LeetCode设计使用 ,分割
            String[] inputArray = inputArgs.split(",");
            javaCmdArr = ArrayUtil.append(UserJudgeConstant.DOCKER_JAVA_EXEC_CMD, inputArray);
        }
        // 此时输入参数为 null，代表只有编译指令
        ExecCreateCmdResponse cmdResponse = dockerClient.execCreateCmd(containerId)
                .withCmd(javaCmdArr)
                .withAttachStderr(true)
                .withAttachStdin(true)
                .withAttachStdout(true)
                .exec();
        return cmdResponse.getId();
    }

    /**
     * 删除用户挂载卷代码
     */
    private void deleteUserCodeFile() {
        FileUtil.del(userCodeFileName);
    }

    //创建并返回用户代码的文件 -之前未每个用户创建了不同的挂载卷目录
    private void createUserCodeFile(String totalCode) {
        String codeDir = dockerSandBoxPool.getCodeDir(containerId);
        userCodeFileName = codeDir + File.separator + UserJudgeConstant.USER_CODE_JAVA_CLASS_NAME;
        //如果文件之前存在，将之前的文件删除掉
        if (FileUtil.exist(userCodeFileName)) {
            FileUtil.del(userCodeFileName);
        }
        FileUtil.writeString(totalCode, userCodeFileName, SystemConstant.UTF8);
    }
}
